iT邦幫忙

2024 iThome 鐵人賽

DAY 23
1
Modern Web

Vue 3 初學者:用實作帶你看過核心概念系列 第 23

Vue 3 用實作帶你看過核心概念 - Day 23:依賴注入( Provide-Inject )

  • 分享至 

  • xImage
  •  

目錄

  • Prop 逐級透傳問題(Prop Drilling)
  • Provide-Inject 基礎使用
  • Inject 選項屬性介紹
  • Provide 函數定義 - 讀取 this 上下文
  • 結論

Prop 逐級透傳問題(Prop Drilling)

Dayxx中提到過,父子組件之間的數據傳遞可以通過props來實現。然而,當祖孫層組件需要進行數據傳遞時,中間層級的組件可能並不需要這個props,但卻不得不為了使子組件獲取數據而被迫傳遞。
props 逐級透傳問題官方示意圖

為了解決這個逐級透傳的問題,我們可以使用provide(依賴提供者)inject(依賴注入者),這樣可以讓祖先組件直接提供數據給深層的子組件,避免不必要的props傳遞。
provide-inject 解決逐級傳遞官方示意圖

Provide-Inject 基礎使用

使用inject注入的深層組件可以讀取祖先組件透過provide提供的值。這種傳遞方式與props類似,同樣可以在深層組件中進行定義。

以下範例展示了祖先組件、中間組件和深層組件之間的關係,並演示了如何通過祖先組件傳值(message):

👉 Vue3 Options API 依賴灌注 - Provide & Inject 實作連結

provide-inject 範例呈現圖

祖先組件 Vue Template:

<div id="app">
  <child-component></child-component>
</div>

中間層組件 Vue Template:

<template id="childComponent">
  <div class="container child">
    <h2>我是 childComponent</h2>
    <deep-child-component></deep-child-component>
  </div>
</template>

深層組件 Vue Template:

<template id="deepChildComponent">
  <div class="container deepChild">
    <h2>我是 deepChildComponent</h2>
    <p> message:{{ message }}</p>
  </div>
</template>

祖先組件 javascript:

const rootComponent = {
  provide: { message: "hello world Vue!" },
  components: { childComponent }
};

深層組件 javascript:

const deepChildComponent = {
  inject: ["message"],
  template: "#deepChildComponent"
};

但需要注意,如果中間層和祖先層同時使用相同名稱的provide提供變數,深層組件讀取到的會是由中間層provide提供的變數。因此,應謹慎管理 provide的層級,以避免混淆和不必要的覆蓋。

👉 Vue3 Options API 依賴灌注 - Provide & Inject (中間層 provide 覆蓋)實作連結

將上面案例中的中間層修改一下:

中間層 javascript:

const childComponent = {
  template: "#childComponent",
  provide: { message: "hello world React!" },
  components: { deepChildComponent }
};

會發現深層的deepChildComponent組件inject的值,被中間層替換到。

Inject 選項屬性介紹

在子組件的inject配置中,可以使用對象來定義fromdefault屬性,以設置依賴注入。

  • from:可以指定要注入的依賴名稱,並且改為另一個名稱來使用。
  • default:對於基礎類型數據,可以直接提供預設值;若是對象類型,則應使用工廠函數來確保每個組件實例都有獨立的數據。

👉 Vue3 Options API 依賴灌注 - Inject 選項實作連結

祖先組件 javascript:

const rootComponent = {
  provide: { message: "hello world Vue!" },
  components: { childComponent }
};

深層組件 javascript:

const deepChildComponent = {
  inject: {
    newMessageName: {
      from: "message",
    }
  },
  template: "#deepChildComponent"
};

深層組件 Vue Template:

<template id="deepChildComponent">
  <div class="container deepChild">
    <h2>我是 deepChildComponent</h2>
    <p> message:{{ newMessageName }}</p>
  </div>
</template>

如果inject注入的深層組件找不到對應的provide值,Vue會在開發環境中顯示警告。

inject 未讀取到 provide 值警告

這時候可以搭配default設置inject的預設值:

深層組件 javascrpit:

const deepChildComponent = {
  inject: {
    newMessageName: {
      from: "message",
      default: "我是預設值"
    }
  },
  template: "#deepChildComponent"
};

Provide 函數定義 - 讀取 this 上下文

默認情況下,Provide-Inject使用對象定義時,會在組件初始化階段執行,因此無法在這個階段獲取this的上下文資訊。如果需要獲取this上下文的資訊,必須通過函數的方式定義provide,這樣每次被讀取時都會執行該函數,從而回傳最新的值。

👉 Vue3 Options API 依賴灌注 - Provide & Inject 函數讀取 this 初始值實作連結

以下是一個祖先組件定義計數器的案例,其中使用data中的數據作為預設值,並將其提供給深層子組件使用:

數據使用 inject 值當作初始值範例圖

祖先組件 Vue Template:

<div id="app">
  <button @click="add" type="button">+ 1</button>
  <h2>祖先組件顯示當前 count 變數值:{{ this.count }}</h2>
  <child-component></child-component>
</div>

祖先組件 javascript:

const rootComponent = {
  provide() {
    return { message: `告訴你計數器數字:${this.count}` };
  },
  data() {
    return {
      count: 10
    };
  },
  methods: {
    add() {
      this.count += 1;
    }
  },
  components: { childComponent }
}

深層組件 Vue Template:

<template id="deepChildComponent">
  <div class="container deepChild">
    <h2>我是 deepChildComponent</h2>
    <p> message:{{ message }}</p>
  </div>
</template>

深層組件 javascript:

const deepChildComponent = {
  inject: ["message"],
  template: "#deepChildComponent"
};

data中的數據在透過provide/inject傳遞時,預設並不會建立響應式連結。因此,即使父組件透過方法更新了data中的變數,使用inject的子組件也不會自動同步更新。

如果需要讓被inject的子組件與祖先組件中的data中的變數保持響應式連結,可以使用computed 函數來實現(這在Composition API中經常使用)。

👉 Vue3 Options API 依賴灌注 - Provide & Inject 函數讀取 this 初始值並使用計算屬性實作連結

祖先組件 javascript:

const rootComponent = {
  data() {
    return {
      count: 10
    };
  },
  methods: {
    add() {
      this.count += 1;
    }
  },
  provide() {
    return {
      message: computed(() => this.count * 2)
    };
  },
  components: { childComponent }
};

結論

  • Provide-Inject 垂直跨組件傳值provideinject能夠有效解決props的逐級傳遞問題,允許祖先組件直接與深層子組件共享數據,避免中間組件被迫透傳不需要的數據。當多個層級的組件提供了同名的 provide值時,注入的組件會優先使用最近的provide
  • 響應式的數據注入:默認情況下,provide中提供的數據不是響應式的。如果需要保持數據的響應性,可以利用computed()計算屬性函數來提供響應式的值。
  • inject 選項參數:在子組件中使用inject時,可以通過from 修改注入依賴的名稱,並通過default設定當找不到對應provide時的預設值。對於物件類型,建議使用工廠函數,以確保每個組件實例都擁有獨立的數據。

上一篇
Vue 3 用實作帶你看過核心概念 - Day 22:v-slot 插槽使用
下一篇
Vue 3 用實作帶你看過核心概念 - Day 24:異步加載(Lazy loading)
系列文
Vue 3 初學者:用實作帶你看過核心概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言